﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;

namespace Red.Toolkit.Profiler.Logic.Interpreters
{
    class FileSystemTraceInterpreter : ProfilerDataView
    {
        public FileSystemTraceInterpreter( ProfilerData data, ProfilerTraceInstance trace )
            : base( "I/O Trace", data.FilePath, data, trace )
        {
            Style.SetColor( (int)DisplayType.SyncLoad, Color.FromArgb( 255, 200, 200, 100 ) );
            Style.SetColor( (int)DisplayType.SyncSeek, Color.FromArgb( 255, 200, 100, 100 ) );
            Style.SetColor( (int)DisplayType.AsyncLoad, Color.FromArgb( 255, 100, 200, 200 ) );
            Style.SetColor( (int)DisplayType.Decompression, Color.FromArgb( 255, 100, 100, 200 ) );
            Style.SetColor( (int)DisplayType.Deserialization, Color.FromArgb( 255, 100, 200, 100 ) );
            Style.SetColor( (int)DisplayType.ResourceLoad, Color.FromArgb( 255, 200, 100, 200 ) );
            Style.SetColor( (int)DisplayType.OpenClose, Color.FromArgb( 255, 150, 150, 150 ) );
            Style.SetColor( (int)DisplayType.InplaceLoad, Color.FromArgb( 255, 50, 200, 50 ) );
            Style.SetColor( (int)DisplayType.LoadingJob, Color.FromArgb( 255, 200, 50, 50 ) );
            Style.SetColor( (int)DisplayType.Invalid, Color.FromArgb( 255, 255, 0, 0 ) );
            Style.SetColor( (int)DisplayType.UserBlock, Color.FromArgb( 255, 150, 150, 200 ) );
            Style.SetColor( (int)DisplayType.LoadingScreen, Color.FromArgb( 255, 120, 120, 120 ) );
            Style.SetColor( (int)DisplayType.GC, Color.FromArgb( 255, 80, 80, 80 ) );

            _PhysicalFiles = new Dictionary<uint, FileSystemFileInfo>();
            _DepotFiles = new Dictionary<uint, FileSystemFileInfo>();
            _IDCounter = 0;
        }

        // display types
        private enum DisplayType
        {
            Selection = 0,
            SyncLoad = 1,
            SyncSeek = 2,
            AsyncLoad = 3,
            Decompression = 4,
            Deserialization = 5,
            ResourceLoad = 7,
            OpenClose = 8,
            InplaceLoad = 9,
            LoadingJob = 10,
            UserBlock = 11,
            LoadingScreen = 12,
            GC = 13,
            Invalid = 14,
        };

        // counter types (must match C++)
        private enum CounterType
        {
            MemoryBlocks = 1,
            DecompressionTasks = 3,
            PendingFiles = 4,
        };

        // general signals (must match C++)
        private enum SignalType
        {
            FileAsyncReadScheduled = 1,
            LoadingScreenOn = 2,
            LoadingScreenOff = 3,
            BlackScreenOn = 4,
            BlackScreenOff = 5,
            VideoPlay = 6,
            VideoStop = 7,
            VideoReadRequested = 8,
            VideoReadCompleted = 9,
            LoadingProfilerBlock = 10,
            LoadingProfilerEnd = 11,

            StreamingCameraPos = 12,
            StreamingCameraDist = 13,
            StreamingEntityStats = 14,
            StreamingSectorStats = 15,
        };

        // general block types (must match C++)
        private enum BlockType
        {
            FileSyncOpen = 1,
            FileSyncClose = 2,
            FileAsyncOpen = 3,
            FileAsyncClose = 4,
            FileAsyncRead = 5,
            FileSyncRead = 6,
            FileSyncSeek = 7,

            LoadResource = 10,
            SyncLoadData = 11,
            SyncDecompression = 12,
            AsyncDecompression = 13,
            Deserialize = 14,
            LoadTables = 15,
            MapTables = 16,
            CreateObjects = 17,
            LoadImports = 18,
            LoadObjects = 19,
            PostLoad = 20,
            DeferredSyncRead = 21,
            CachedTread = 22,
            LoadInplace = 23,
            LoadingJob = 24,
            UserProfileBlock = 25,

            Scene = 30,
            SceneSection = 31,
            StreamingLock = 32,
        };

        private class FileSystemFileInfo
        {
            private string _FilePath;
            private string _ShortName;
            private bool _IsPhysical;
            private uint _InternalID;

            public FileSystemFileInfo( string path, bool physical, uint id )
            {
                _IsPhysical = physical;
                _InternalID = id;
                _FilePath = path;

                int index = path.LastIndexOf( '\\' );
                if ( index != -1 )
                {
                    _ShortName = path.Substring( index + 1 );
                }
                else
                {
                    _ShortName = path;
                }
            }

            public uint ID
            {
                get
                {
                    return _InternalID;
                }
            }

            public bool Physical
            {
                get
                {
                    return _IsPhysical;
                }
            }

            public string ShortName
            {
                get
                {
                    return _ShortName;
                }
            }

            public string Path
            {
                get
                {
                    return _FilePath;
                }
            }
        }

        private class FileAsyncRequest
        {
            public uint ID;
            public ulong Offset;
            public uint Size;
            public uint IOTag;
            public FileSystemFileInfo File;
            public RawEvent Schedule;
            public FileSystemEvent Execute;
        }

        // system files
        Dictionary<uint, FileSystemFileInfo> _PhysicalFiles;
        Dictionary<uint, FileSystemFileInfo> _DepotFiles;
        uint _IDCounter;

        private FileSystemFileInfo GetPhysicalFile( uint nameHash )
        {
            if ( _PhysicalFiles.ContainsKey( nameHash ) )
                return _PhysicalFiles[nameHash];

            string path = profilerData.GetString( nameHash );
            var file = new FileSystemFileInfo( path, true, _IDCounter );
            _PhysicalFiles.Add( nameHash, file );
            _IDCounter += 1;
            return file;
        }

        private FileSystemFileInfo GetDepotFile( uint nameHash )
        {
            if ( _DepotFiles.ContainsKey( nameHash ) )
                return _DepotFiles[nameHash];

            string path = profilerData.GetString( nameHash );
            var file = new FileSystemFileInfo( path, false, _IDCounter );
            _DepotFiles.Add( nameHash, file );
            _IDCounter += 1;
            return file;
        }

        private class FileSystemEvent : Event
        {
            public static DisplayType GetDisplayType( RawEvent e )
            {
                if ( e.Def.Type == ProfilerDefinitionType.Block )
                {
                    BlockType blockId = (BlockType)e.Def.ID;
                    switch ( blockId )
                    {
                        case BlockType.FileSyncOpen:
                        case BlockType.FileSyncClose:
                        case BlockType.FileAsyncOpen:
                        case BlockType.FileAsyncClose:
                            return DisplayType.OpenClose;

                        case BlockType.FileAsyncRead:
                            return DisplayType.AsyncLoad;

                        case BlockType.FileSyncRead:
                            return DisplayType.SyncLoad;

                        case BlockType.FileSyncSeek:
                            return DisplayType.SyncSeek;

                        case BlockType.LoadResource:
                            return DisplayType.ResourceLoad;

                        case BlockType.LoadInplace:
                            return DisplayType.InplaceLoad;

                        case BlockType.SyncDecompression:
                        case BlockType.AsyncDecompression:
                            return DisplayType.Decompression;

                        case BlockType.SyncLoadData:
                        case BlockType.Deserialize:
                        case BlockType.LoadTables:
                        case BlockType.MapTables:
                        case BlockType.CreateObjects:
                        case BlockType.LoadImports:
                        case BlockType.LoadObjects:
                        case BlockType.PostLoad:
                        case BlockType.DeferredSyncRead:
                            return DisplayType.Deserialization;

                        case BlockType.LoadingJob:
                            return DisplayType.LoadingJob;

                        case BlockType.UserProfileBlock:
                            return DisplayType.UserBlock;

                        case BlockType.Scene:
                        case BlockType.SceneSection:
                        case BlockType.StreamingLock:
                            return DisplayType.UserBlock;
                    }
                }

                return DisplayType.Invalid;
            }

            public FileSystemEvent( FileSystemTraceInterpreter parentView, RawEvent evt, Color color )
                : base( parentView, evt, color )
            {
                _File = null;
            }

            public override float GetWeight()
            {
                BlockType blockId = (BlockType)Evt.Def.ID;
                switch ( blockId )
                {
                    case BlockType.FileSyncRead:
                    case BlockType.FileAsyncRead:
                        return (float)_Size;
                }

                return 1.0f;
            }

            public override string GetCaption()
            {
                BlockType blockId = (BlockType)Evt.Def.ID;
                if ( blockId == BlockType.LoadingJob )
                {
                    string ret = "Job: " + _CustomString;
                    return ret;
                }
                else if ( blockId == BlockType.UserProfileBlock )
                {
                    return _CustomString;
                }
                else if ( blockId == BlockType.Scene )
                {
                    string ret = "Scene: " + _CustomString;
                    return ret;
                }
                else if ( blockId == BlockType.SceneSection || blockId == BlockType.StreamingLock )
                {
                    return _CustomString;
                }

                return Evt.Def.Name;
            }

            public void SetFile( FileSystemFileInfo file )
            {
                _File = file;
            }

            public void SetAsync( FileAsyncRequest async )
            {
                _Async = async;
                _File = async.File;
                _Size = async.Size;
                _Offset = async.Offset;
            }

            public void SetSize( uint size )
            {
                _Size = size;
            }

            public void SetOffset( ulong offset )
            {
                _Offset = offset;
            }

            public void SetCustomString( string txt )
            {
                _CustomString = txt;
            }

            public FileSystemFileInfo File
            {
                get
                {
                    return _File;
                }
            }

            public string CustomString
            {
                get
                {
                    return _CustomString;
                }
            }

            public FileAsyncRequest Async
            {
                get
                {
                    return _Async;
                }
            }

            public uint Size
            {
                get
                {
                    return _Size;
                }
            }

            private FileSystemFileInfo _File;
            private FileAsyncRequest _Async;
            private uint _Size;
            private ulong _Offset;
            private string _CustomString;
        }

        protected override bool AnalyzeRawData()
        {
            Group asyncGroup = AddGroup( "Async", false );
            Group sceneGroup = AddGroup( "Scene", true );

            // current physical file mapping (handle -> object)
            Dictionary<uint, FileSystemFileInfo> fileMapping = new Dictionary<uint, FileSystemFileInfo>();
            Dictionary<uint, FileAsyncRequest> asyncRequests = new Dictionary<uint, FileAsyncRequest>();

            // for loading regions
            RawEvent prevRegion = null;

            // analyze async requests
            foreach ( var e in profilerData.Events )
            {
                var evt = e.Value;

                // async files
                if ( evt.Def.Type == ProfilerDefinitionType.Block )
                {
                    BlockType blockType = (BlockType)evt.Def.ID;
                    if ( blockType == BlockType.FileAsyncOpen )
                    {
                        if ( evt.Type == RawEventType.BlockStart )
                        {
                            uint hash = evt.Param( 0 );
                            uint handle = evt.EndEvent.Param( 0 );
                            FileSystemFileInfo file = GetPhysicalFile( hash );
                            fileMapping[handle] = file;
                        }
                    }
                    else if ( blockType == BlockType.FileAsyncClose )
                    {
                        uint handle = evt.Param( 0 );
                        if ( fileMapping.ContainsKey( handle ) )
                        {
                            fileMapping.Remove( handle );
                        }
                    }
                }

                // async reads
                else if ( evt.Def.Type == ProfilerDefinitionType.Signal )
                {
                    SignalType signalType = (SignalType)evt.Def.ID;

                    // async schedule - so we can capture the transaction
                    if ( signalType == SignalType.FileAsyncReadScheduled )
                    {
                        uint handle = evt.Param( 0 );

                        FileAsyncRequest asyncRequest = new FileAsyncRequest();
                        asyncRequest.Schedule = evt;
                        asyncRequest.Execute = null;
                        asyncRequest.ID = evt.Param( 1 );
                        asyncRequest.Offset = (ulong)evt.Param( 2 ) | (((ulong)evt.Param( 3 )) << 32);
                        asyncRequest.Size = evt.Param( 4 );
                        asyncRequest.IOTag = evt.Param( 5 );

                        if ( fileMapping.ContainsKey( handle ) )
                            asyncRequest.File = fileMapping[handle];

                        // add to table
                        asyncRequests[asyncRequest.ID] = asyncRequest;
                    }
                    else if ( signalType == SignalType.LoadingProfilerBlock )
                    {
                        var newRegion = evt;
                        if ( prevRegion != null )
                        {
                            string name = profilerData.Strings[evt.Param( 0 )];
                            if ( name != "<base>" )
                            {
                                AddRegion( evt.ListTime, name, prevRegion.StartTime, evt.StartTime, 0 );
                            }

                            if ( name == "END" )
                                newRegion = null;
                        }
                        prevRegion = newRegion;
                    }
                    else if ( signalType == SignalType.LoadingProfilerEnd )
                    {
                        prevRegion = null;
                    }
                }
            }

            // analyze threads
            Dictionary<ProfilerThread, Group> GroupsForThreads = new Dictionary<ProfilerThread, Group>();
            foreach ( var t in profilerData.Threads )
            {
                // empty or noise thread
                if ( t.Events.Count <= 10 )
                    continue;

                // create group per thread
                string threadName = t.Name;

                // create sub groups
                Group group = AddGroup( threadName, true );
                GroupsForThreads[t] = group;

                // add events
                foreach ( var e in t.Events )
                {
                    var evt = e.Value;

                    // only blocks
                    if ( evt.Def.Type != ProfilerDefinitionType.Block )
                        continue;

                    // get emitted block type
                    BlockType blockType = (BlockType)evt.Def.ID;

                    // async event type
                    if ( blockType == BlockType.FileAsyncClose || blockType == BlockType.FileAsyncOpen ||
                        blockType == BlockType.FileAsyncRead )
                    {
                        if ( evt.Type == RawEventType.BlockStart )
                        {
                            DisplayType dt = FileSystemEvent.GetDisplayType( evt );
                            var de = new FileSystemEvent( this, evt, Style.BlockColors[ (int)dt ] );

                            // file opened
                            if ( blockType == BlockType.FileAsyncOpen )
                            {
                                uint hash = evt.Param( 0 );
                                uint handle = evt.EndEvent.Param( 0 );
                                FileSystemFileInfo file = GetPhysicalFile( hash );
                                fileMapping[handle] = file;
                                de.SetFile( fileMapping[handle] );
                            }
                            // file closed
                            else if ( blockType == BlockType.FileAsyncClose )
                            {
                                uint handle = evt.Param( 0 );
                                if ( fileMapping.ContainsKey( handle ) )
                                {
                                    de.SetFile( fileMapping[handle] );
                                    fileMapping.Remove( handle );
                                }
                            }
                            // file read
                            else if ( blockType == BlockType.FileAsyncRead )
                            {
                                uint id = evt.Param( 0 );
                                if ( asyncRequests.ContainsKey( id ) )
                                {
                                    FileAsyncRequest info = asyncRequests[id];
                                    info.Execute = de;
                                    de.SetAsync( info );
                                }
                            }

                            AddEvent( de, asyncGroup );
                        }

                        continue;
                    }

                    // scene event
                    if ( blockType == BlockType.Scene || blockType == BlockType.SceneSection || blockType == BlockType.StreamingLock )
                    {
                        if ( evt.Type == RawEventType.BlockStart )
                        {
                            // short assignedDepth = (short)((blockType == BlockType.StreamingLock) ? 0 : (1 + sceneDepth));
                            DisplayType dt = FileSystemEvent.GetDisplayType( evt );
                            var de = new FileSystemEvent( this, evt, Style.BlockColors[(int)dt] );

                            if ( blockType == BlockType.Scene || blockType == BlockType.SceneSection )
                            {
                                uint nameHash = evt.Param( 0 );
                                string path = profilerData.GetString( nameHash );
                                de.SetCustomString( path );
                            }
                            else if ( blockType == BlockType.StreamingLock )
                            {
                                uint nameHash = evt.Param( 0 );
                                string path = profilerData.GetString( nameHash );
                                de.SetCustomString( path );
                            }

                            AddEvent( de, sceneGroup );
                        }
                        else if ( evt.Type == RawEventType.BlockEnd )
                        {
                            if ( blockType == BlockType.Scene || blockType == BlockType.SceneSection )
                            {

                            }
                        }

                        continue;
                    }

                    // system event type
                    if ( blockType == BlockType.FileSyncClose || blockType == BlockType.FileSyncOpen ||
                        blockType == BlockType.FileSyncRead || blockType == BlockType.FileSyncSeek )
                    {
                        if ( evt.Type == RawEventType.BlockStart )
                        {
                            DisplayType dt = FileSystemEvent.GetDisplayType( evt );
                            var de = new FileSystemEvent( this, evt, Style.BlockColors[(int)dt] );

                            // file opened
                            if ( blockType == BlockType.FileSyncOpen )
                            {
                                uint hash = evt.Param( 0 );
                                uint handle = evt.EndEvent.Param( 0 );
                                FileSystemFileInfo file = GetPhysicalFile( hash );
                                fileMapping[handle] = file;
                                de.SetFile( fileMapping[handle] );
                            }
                            // file closed
                            else if ( blockType == BlockType.FileSyncClose )
                            {
                                uint handle = evt.Param( 0 );
                                if ( fileMapping.ContainsKey( handle ) )
                                {
                                    de.SetFile( fileMapping[handle] );
                                    fileMapping.Remove( handle );
                                }
                            }
                            // file read
                            else if ( blockType == BlockType.FileSyncRead )
                            {
                                uint handle = evt.Param( 0 );
                                if ( fileMapping.ContainsKey( handle ) )
                                {
                                    FileSystemFileInfo file = fileMapping[handle];
                                    de.SetFile( file );
                                    de.SetSize( evt.Param( 1 ) );
                                }
                            }
                            // file seek
                            else if ( blockType == BlockType.FileSyncSeek )
                            {
                                uint handle = evt.Param( 0 );
                                if ( fileMapping.ContainsKey( handle ) )
                                {
                                    FileSystemFileInfo file = fileMapping[handle];
                                    de.SetFile( file );
                                    de.SetOffset( evt.Param( 1 ) | (((ulong)evt.Param( 2 )) << 32) );
                                }
                            }

                            AddEvent( de, group );
                        }

                        continue;
                    }

                    // engine event
                    if ( evt.Type == RawEventType.BlockEnd )
                    {
                        continue;
                    }

                    // create display event
                    if ( evt.Type == RawEventType.BlockStart )
                    {
                        DisplayType dt = FileSystemEvent.GetDisplayType( evt );
                        var de = new FileSystemEvent( this, evt, Style.BlockColors[(int)dt] );

                        // depot file
                        if ( blockType == BlockType.LoadResource || blockType == BlockType.Deserialize ||
                            blockType == BlockType.LoadTables || blockType == BlockType.MapTables ||
                            blockType == BlockType.CreateObjects || blockType == BlockType.LoadImports || blockType == BlockType.LoadInplace ||
                            blockType == BlockType.PostLoad || blockType == BlockType.LoadObjects )
                        {
                            uint fileHash = evt.Param( 0 );
                            FileSystemFileInfo file = GetDepotFile( fileHash );
                            de.SetFile( file );
                        }

                        // job
                        if ( blockType == BlockType.LoadingJob )
                        {
                            uint nameHash = evt.Param( 0 );
                            string jobName = profilerData.GetString( nameHash );
                            de.SetCustomString( jobName );
                        }

                        // user profile block
                        if ( blockType == BlockType.UserProfileBlock )
                        {
                            uint nameHash = evt.Param( 0 );
                            string blockName = profilerData.GetString( nameHash );
                            de.SetCustomString( blockName );

                            /*
                            if ( blockName == "GC" )
                                de.SetDisplayType( DisplayType.GC );
                            else if ( blockName == "LoadingScreen" )
                                de.SetDisplayType( DisplayType.LoadingScreen );
                            else if ( blockName == "BlackScreen" )
                                de.SetDisplayType( DisplayType.LoadingScreen );
                                */
                        }

                        // decompression
                        if ( blockType == BlockType.SyncDecompression || blockType == BlockType.AsyncDecompression )
                        {
                            uint size = evt.Param( 0 );
                            de.SetSize( size );
                        }

                        AddEvent( de, group );
                    }
                }
            }

            /*
            // link async requests
            foreach (var async in asyncRequests.Values)
            {
                if (async.Execute != null && async.Schedule != null)
                {
                    var group = GroupsForThreads[ async.Schedule.Thread ];
                    var time = async.Schedule.StartTime;
                    async.Execute.SetSource(group, time);
                }
            }
            */

            RegisterGraphs();

            // done
            return true;
        }

        private class FileAccessInfo
        {
            public FileSystemFileInfo File;
            public ulong Size;
            public ulong Count;

            public FileAccessInfo( FileSystemFileInfo file )
            {
                File = file;
                Size = 0;
                Count = 0;
            }
        }

        public override string GetDescription( IList<Event> events )
        {
            string txt = "";

            // file collector
            Dictionary<FileSystemFileInfo, FileAccessInfo> physicalFiles = new Dictionary<FileSystemFileInfo, FileAccessInfo>();
            HashSet<FileSystemFileInfo> depotFiles = new HashSet<FileSystemFileInfo>();

            // compute stats
            ulong totalAsyncReadSize = 0;
            uint totalAsyncReadOps = 0;
            double totalAsyncReadTime = 0;
            ulong totalSyncReadSize = 0;
            uint totalSyncReadOps = 0;
            double totalSyncReadTime = 0;
            uint totalSyncSeekOps = 0;
            double totalSyncSeekTime = 0;
            double totalCreateObjectsTime = 0;
            uint totalCreatedObjects = 0;
            double totalLoadObjectsTime = 0;
            double totalPostLoadTimeTime = 0;
            double totalLoadTablesTime = 0;
            double totalMapTablesTime = 0;
            double totalLoadImportsTime = 0;
            bool hasDeserialization = false;
            uint totalLoadObjectsCount = 0;

            // waiting
            double totalWaitTime = 0;
            double maxWaitTime = 0;
            uint numWaitingAsyncOps = 0;

            // decompression types
            ulong[] totalDecompressionSize = new ulong[7];
            double[] totalDecompressionTime = new double[7];
            ulong totalAllDecompressionSize = 0;
            double totalAllDecompressionTime = 0;

            // ioTags
            ulong[] ioTagsCount = new ulong[64];
            ulong[] ioTagsSize = new ulong[64];
            double[] ioTagsWaitTime = new double[64];
            double[] ioTagsReadTime = new double[64];

            // compression names
            string[] compressionNames = new string[7];
            compressionNames[0] = "None";
            compressionNames[1] = "zlib";
            compressionNames[2] = "snappy";
            compressionNames[3] = "doboz";
            compressionNames[4] = "lz4";
            compressionNames[5] = "lz4hc";
            compressionNames[6] = "c-zlib";

            int totalFileSystemEvents = 0;
            foreach ( var e in events )
            {
                FileSystemEvent de = (FileSystemEvent)e;
                if ( de.Evt.Type != RawEventType.BlockStart )
                    continue;

                totalFileSystemEvents++;

                double length = e.Evt.EndTime - e.Evt.StartTime;

                BlockType blockType = (BlockType)de.Evt.Def.ID;
                uint accessSize = 0;

                if ( blockType == BlockType.FileSyncRead )
                {
                    totalSyncReadOps += 1;
                    totalSyncReadTime += length;
                    totalSyncReadSize += de.Size;
                    accessSize = de.Size;
                }
                else if ( blockType == BlockType.FileSyncSeek )
                {
                    totalSyncSeekOps += 1;
                    totalSyncSeekTime += length;
                }
                else if ( blockType == BlockType.FileAsyncRead )
                {
                    totalAsyncReadOps += 1;
                    totalAsyncReadTime += length;
                    totalAsyncReadSize += de.Size;
                    accessSize = de.Size;


                    if ( de.Async != null )
                    {
                        int tag = (int)de.Async.Schedule.Param( 5 ); // ioTag
                        ioTagsCount[tag] += 1;
                        ioTagsSize[tag] += de.Size;
                        ioTagsReadTime[tag] += length;

                        if ( de.Async.Execute != null && de.Async.Schedule != null )
                        {
                            double waitTime = (de.Async.Execute.Evt.StartTime - de.Async.Schedule.StartTime);
                            ioTagsWaitTime[tag] += waitTime;

                            if ( waitTime > maxWaitTime )
                                maxWaitTime = waitTime;

                            totalWaitTime += waitTime;
                            numWaitingAsyncOps += 1;
                        }
                    }
                }
                else if ( blockType == BlockType.LoadObjects )
                {
                    totalLoadObjectsCount += 1;
                    totalLoadObjectsTime += length;
                    hasDeserialization = true;
                }
                else if ( blockType == BlockType.CreateObjects )
                {
                    totalCreateObjectsTime += length;
                    totalCreatedObjects += de.Evt.Param( 1 );
                    hasDeserialization = true;
                }
                else if ( blockType == BlockType.PostLoad )
                {
                    hasDeserialization = true;
                    totalPostLoadTimeTime += length;
                }
                else if ( blockType == BlockType.LoadTables )
                {
                    hasDeserialization = true;
                    totalLoadTablesTime += length;
                }
                else if ( blockType == BlockType.MapTables )
                {
                    hasDeserialization = true;
                    totalMapTablesTime += length;
                }
                else if ( blockType == BlockType.LoadImports )
                {
                    hasDeserialization = true;
                    totalLoadImportsTime += length;
                }
                else if ( blockType == BlockType.SyncDecompression || blockType == BlockType.AsyncDecompression )
                {
                    int type = (int)de.Evt.Param( 1 ); // type
                    totalDecompressionSize[type] += de.Evt.Param( 0 );
                    totalDecompressionTime[type] += length;
                    totalAllDecompressionSize += de.Evt.Param( 0 );
                    totalAllDecompressionTime += length;
                }

                if ( de.File != null )
                {
                    if ( de.File.Physical )
                    {
                        FileAccessInfo accessInfo = null;
                        if ( physicalFiles.ContainsKey( de.File ) )
                        {
                            accessInfo = physicalFiles[de.File];
                        }
                        else
                        {
                            accessInfo = new FileAccessInfo( de.File );
                            physicalFiles.Add( de.File, accessInfo );
                        }

                        if ( accessSize > 0 )
                        {
                            accessInfo.Size += accessSize;
                            accessInfo.Count += 1;
                        }
                    }
                    else
                    {
                        if ( !depotFiles.Contains( de.File ) )
                            depotFiles.Add( de.File );
                    }
                }
            }

            txt += string.Format( "Total filesystem events: {0}\n", totalFileSystemEvents );

            // sync data
            if ( totalSyncReadOps > 0 )
            {
                txt += string.Format( "Sync read op speed: {0}/s\n",
                    Utils.FormatValue( totalSyncReadSize / totalSyncReadTime, ValueType.DataSize ) );
                txt += string.Format( "Sync read size: {0} ({1})\n", totalSyncReadSize,
                    Utils.FormatValue( totalSyncReadSize, ValueType.DataSize ) );
                txt += string.Format( "Sync read time: {0}\n",
                    Utils.FormatValue( totalSyncReadTime, ValueType.Time ) );
                txt += string.Format( "Sync read ops: {0}\n\n", totalSyncReadOps );
            }

            // sync seeks
            if ( totalSyncSeekOps > 0 )
            {
                txt += string.Format( "Seek ops: {0}/s\n", totalSyncSeekOps );
                txt += string.Format( "Seek time: {0}\n\n", Utils.FormatValue( totalSyncSeekTime, ValueType.Time ) );
            }

            // async data
            if ( totalAsyncReadOps > 0 )
            {
                txt += string.Format( "Async read op speed: {0}\n",
                    Utils.FormatValue( totalAsyncReadSize / totalAsyncReadTime, ValueType.DataSpeed ) );
                txt += string.Format( "Async read size: {0} ({1})\n", totalAsyncReadSize,
                    Utils.FormatValue( totalAsyncReadSize, ValueType.DataSize ) );
                txt += string.Format( "Async read time: {0}\n",
                    Utils.FormatValue( totalAsyncReadTime, ValueType.Time ) );
                txt += string.Format( "Async read ops: {0}\n\n", totalAsyncReadOps );

                if ( numWaitingAsyncOps > 0 )
                {
                    txt += string.Format( "Async wait ops: {0}\n", numWaitingAsyncOps );
                    txt += string.Format( "Async average wait: {0}\n",
                        Utils.FormatValue( totalWaitTime / numWaitingAsyncOps, ValueType.Time ) );
                    txt += string.Format( "Async max wait: {0}\n\n",
                        Utils.FormatValue( maxWaitTime, ValueType.Time ) );
                }

                // IO tag stats
                var tagNames = GetIOTagNames();
                for ( int n = 0; n < tagNames.Length; ++n )
                {
                    if ( ioTagsCount[n] > 0 )
                    {
                        txt += string.Format( "IOTag {0}: {1} blocks ({2}) in {3} ({4}). Total Wait: {5}.\n",
                            tagNames[n],
                            ioTagsCount[n],
                            Utils.FormatValue( ioTagsSize[n], ValueType.DataSize ),
                            Utils.FormatValue( ioTagsReadTime[n], ValueType.Time ),
                            ioTagsReadTime[n] > 0.0 ? Utils.FormatValue( ioTagsSize[n] / ioTagsReadTime[n], ValueType.DataSpeed ) : "Inf",
                            Utils.FormatValue( ioTagsWaitTime[n], ValueType.Time ) );
                    }
                }
                txt += "\n";
            }

            if ( totalAllDecompressionSize > 0 )
            {
                txt += string.Format( "All decompression size: {0}\n",
                    Utils.FormatValue( totalAllDecompressionSize, ValueType.DataSize ) );
                txt += string.Format( "All decompression time: {0} (speed: {1})\n",
                    Utils.FormatValue( totalAllDecompressionTime, ValueType.Time ),
                    Utils.FormatValue( totalAllDecompressionSize / totalAllDecompressionTime, ValueType.DataSpeed ) );

                for ( int i = 0; i < 7; ++i )
                {
                    if ( totalDecompressionSize[i] > 0 )
                    {
                        txt += string.Format( "  - {0} size: {1}\n", compressionNames[i],
                            Utils.FormatValue( totalDecompressionSize[i], ValueType.DataSize ) );
                        txt += string.Format( "  - {0} time: {1} (speed: {2})\n\n", compressionNames[i],
                            Utils.FormatValue( totalDecompressionTime[i], ValueType.Time ),
                            Utils.FormatValue( totalDecompressionSize[i] / totalDecompressionTime[i], ValueType.DataSpeed ) );
                    }
                }

                txt += "\n";
            }

            if ( hasDeserialization )
            {
                if ( depotFiles.Count > 0 )
                {
                    txt += string.Format( "File count: {0}\n", depotFiles.Count );
                }

                txt += string.Format( "Object count: {0}\n", totalCreatedObjects );
                txt += string.Format( "Create objects: {0}\n", Utils.FormatValue( totalCreateObjectsTime, ValueType.Time ) );
                txt += string.Format( "Load imports: {0}\n", Utils.FormatValue( totalLoadImportsTime, ValueType.Time ) );
                txt += string.Format( "Load objects: {0}\n", Utils.FormatValue( totalLoadObjectsTime, ValueType.Time ) );
                txt += string.Format( "Load tables: {0}\n", Utils.FormatValue( totalLoadTablesTime, ValueType.Time ) );
                txt += string.Format( "Map tables: {0}\n", Utils.FormatValue( totalMapTablesTime, ValueType.Time ) );
                txt += string.Format( "PostLoad objects: {0}\n", Utils.FormatValue( totalPostLoadTimeTime, ValueType.Time ) );
            }

            if ( physicalFiles.Count > 0 )
            {
                txt += string.Format( "Physical files accessed: {0}\n", physicalFiles.Count );
                int index = 0;

                List<FileAccessInfo> list = physicalFiles.Values.OrderByDescending( o => o.Size ).ToList();
                foreach ( var f in list )
                {
                    txt += " " + index.ToString() + ":  ";
                    txt += Utils.FormatValue( f.Size, ValueType.DataSize );
                    txt += "  [ ";
                    txt += f.File.ShortName;
                    txt += " ]\n";
                    index += 1;
                }
            }

            return txt;
        }


        private void FillGraphFromCounters( Graph graph, CounterType type, Int64 startValue )
        {
            Int64 value = startValue;
            graph.AddPoint( 0, value );
            foreach ( var e in profilerData.Events )
            {
                if ( e.Value.Type == RawEventType.CounterInc )
                {
                    CounterType counterType = (CounterType)e.Value.Def.ID;
                    if ( counterType == type )
                    {
                        value += (Int64)e.Value.Param( 0 );
                        graph.AddPoint( e.Value.StartTime, value );
                    }
                }
                else if ( e.Value.Type == RawEventType.CounterDec )
                {
                    CounterType counterType = (CounterType)e.Value.Def.ID;
                    if ( counterType == type )
                    {
                        value -= (Int64)e.Value.Param( 0 );
                        graph.AddPoint( e.Value.StartTime, value );
                    }
                }
            }
        }

        private void FillGraphFromCounterNumber( Graph graph, CounterType type, Int64 startValue )
        {
            Int64 value = startValue;
            graph.AddPoint( 0, value );
            foreach ( var e in profilerData.Events )
            {
                if ( e.Value.Type == RawEventType.CounterInc )
                {
                    CounterType counterType = (CounterType)e.Value.Def.ID;
                    if ( counterType == type )
                    {
                        value += 1;
                        graph.AddPoint( e.Value.StartTime, value );
                    }
                }
                else if ( e.Value.Type == RawEventType.CounterDec )
                {
                    CounterType counterType = (CounterType)e.Value.Def.ID;
                    if ( counterType == type )
                    {
                        value -= 1;
                        graph.AddPoint( e.Value.StartTime, value );
                    }
                }
            }
        }

        private void RegisterGraphs()
        {
            graphs.Add( new Graph( "Total data read", ValueType.DataSize, new Graph.GraphCreationDelegate( CreateTotalDataReadGraph ) ) );
            graphs.Add( new Graph( "Pending files", ValueType.GenericInt, new Graph.GraphCreationDelegate( CreatePendingFilesGraph ) ) );
            graphs.Add( new Graph( "Decompression tasks", ValueType.GenericInt, new Graph.GraphCreationDelegate( CreateDecompressionTasksGraph ) ) );
            graphs.Add( new Graph( "IO memory blocks", ValueType.GenericInt, new Graph.GraphCreationDelegate( CreateIOMemBlocksGraph ) ) );
            graphs.Add( new Graph( "Pending async ops", ValueType.GenericInt, new Graph.GraphCreationDelegate( CreatePendingAsyncOpsGraph ) ) );
            graphs.Add( new Graph( "Pending async size", ValueType.DataSize, new Graph.GraphCreationDelegate( CreatePendingAsyncSizeGraph ) ) );
            graphs.Add( new Graph( "Async queue time", ValueType.Time, new Graph.GraphCreationDelegate( CreateAsyncQueueTimeGraph ) ) );
            graphs.Add( new Graph( "Async accumulated queue time", ValueType.Time, new Graph.GraphCreationDelegate( CreateAsyncAccumulatedQueueTimeGraph ) ) );
            graphs.Add( new Graph( "Async wait time", ValueType.Time, new Graph.GraphCreationDelegate( CreateAsyncWaitTimeGraph ) ) );
            graphs.Add( new Graph( "Async accumulated wait time", ValueType.Time, new Graph.GraphCreationDelegate( CreateAsyncAccumulatedWaitTimeGraph ) ) );
            graphs.Add( new Graph( "Async absolute speed", ValueType.DataSpeed, new Graph.GraphCreationDelegate( CreateAsyncAbsoluteSpeedGraph ) ) );
        }

        private void CreatePendingFilesGraph( Graph graph ) // Pending files
        {
            FillGraphFromCounters( graph, CounterType.PendingFiles, 0 );
        }

        private void CreateDecompressionTasksGraph( Graph graph ) // Decompression tasks
        {
            FillGraphFromCounters( graph, CounterType.DecompressionTasks, 0 );
        }

        private void CreateIOMemBlocksGraph( Graph graph ) // IO memory blocks
        {
            FillGraphFromCounterNumber( graph, CounterType.MemoryBlocks, 0 );
        }

        private void CreatePendingAsyncOpsGraph( Graph graph ) // Pending async ops
        {
            Dictionary<uint, uint> pendingRequests = new Dictionary<uint, uint>();
            long trackValue = 0;
            foreach ( var e in profilerData.Events )
            {
                if ( e.Value.Type == RawEventType.Signal )
                {
                    SignalType type = (SignalType)e.Value.Def.ID;
                    if ( type == SignalType.FileAsyncReadScheduled )
                    {
                        uint id = e.Value.Param( 1 ); // id
                        trackValue += 1;
                        graph.AddPoint( e.Value.StartTime, trackValue );
                        pendingRequests.Add( id, 1 );
                    }
                }
                else if ( e.Value.Type == RawEventType.BlockStart )
                {
                    BlockType type = (BlockType)e.Value.Def.ID;
                    if ( type == BlockType.FileAsyncRead )
                    {
                        uint id = e.Value.Param( 0 ); // id
                        if ( pendingRequests.ContainsKey( id ) )
                        {
                            pendingRequests.Remove( id );
                            trackValue -= 1;
                            graph.AddPoint( e.Value.StartTime, trackValue );
                        }
                    }
                }
            }
        }

        private void CreatePendingAsyncSizeGraph( Graph graph ) // Pending async size
        {
            Dictionary<uint, uint> pendingRequests = new Dictionary<uint, uint>();
            long trackValue = 0;
            foreach ( var e in profilerData.Events )
            {
                if ( e.Value.Type == RawEventType.Signal )
                {
                    SignalType type = (SignalType)e.Value.Def.ID;
                    if ( type == SignalType.FileAsyncReadScheduled )
                    {
                        uint id = e.Value.Param( 1 ); // id
                        uint size = e.Value.Param( 4 ); // size

                        trackValue += size;
                        graph.AddPoint( e.Value.StartTime, trackValue );

                        pendingRequests.Add( id, size );
                        graph.AddPoint( e.Value.StartTime, trackValue );
                    }
                }
                else if ( e.Value.Type == RawEventType.BlockStart )
                {
                    BlockType type = (BlockType)e.Value.Def.ID;
                    if ( type == BlockType.FileAsyncRead )
                    {
                        uint id = e.Value.Param( 0 ); // id
                        if ( pendingRequests.ContainsKey( id ) )
                        {
                            uint size = pendingRequests[id];
                            pendingRequests.Remove( id );

                            trackValue -= size;
                            graph.AddPoint( e.Value.StartTime, trackValue );
                        }
                    }
                }
            }
        }
        private void CreateTotalDataReadGraph( Graph graph ) // Total data read
        {
            long totalSize = 0;
            foreach ( var e in allEvents )
            {
                if ( e.Value.Evt.Type == RawEventType.BlockStart )
                {
                    BlockType type = (BlockType)e.Value.Evt.Def.ID;
                    if ( type == BlockType.FileSyncRead || type == BlockType.FileAsyncRead )
                    {
                        FileSystemEvent evt = (FileSystemEvent)e.Value;
                        totalSize += evt.Size;
                        graph.AddPoint( e.Value.Evt.EndTime, totalSize );
                    }
                }
            }
        }

        private void CreateAsyncQueueTimeGraph( Graph graph ) // Async queue time
        {
            foreach ( var e in allEvents )
            {
                if ( e.Value.Evt.Type == RawEventType.BlockStart )
                {
                    BlockType type = (BlockType)e.Value.Evt.Def.ID;
                    if ( type == BlockType.FileAsyncRead )
                    {
                        FileSystemEvent de = (FileSystemEvent)e.Value;
                        if ( de.Async.Execute != null && de.Async.Schedule != null )
                        {
                            double scheduleTime = de.Async.Schedule.StartTime;
                            double executeTime = de.Async.Execute.Evt.StartTime;
                            double executeEndTime = de.Async.Execute.Evt.EndTime;
                            if ( executeTime >= scheduleTime )
                                graph.AddPoint( executeEndTime, executeTime - scheduleTime );
                        }
                    }
                }
            }
        }

        private void CreateAsyncAccumulatedQueueTimeGraph( Graph graph ) // Async accumulated queue time
        {
            double accumulatedTime = 0.0;
            foreach ( var e in allEvents )
            {
                if ( e.Value.Evt.Type == RawEventType.BlockStart )
                {
                    BlockType type = (BlockType)e.Value.Evt.Def.ID;
                    if ( type == BlockType.FileAsyncRead )
                    {
                        FileSystemEvent de = (FileSystemEvent)e.Value;
                        if ( de.Async.Execute != null && de.Async.Schedule != null )
                        {
                            double scheduleTime = de.Async.Schedule.StartTime;
                            double executeTime = de.Async.Execute.Evt.EndTime;
                            if ( executeTime >= scheduleTime )
                            {
                                accumulatedTime += (executeTime - scheduleTime);
                                graph.AddPoint( executeTime, accumulatedTime );
                            }
                        }
                    }
                }
            }
        }

        private void CreateAsyncWaitTimeGraph( Graph graph ) // Async wait time
        {
            foreach ( var e in allEvents )
            {
                if ( e.Value.Evt.Type == RawEventType.BlockStart )
                {
                    BlockType type = (BlockType)e.Value.Evt.Def.ID;
                    if ( type == BlockType.FileAsyncRead )
                    {
                        FileSystemEvent de = (FileSystemEvent)e.Value;
                        if ( de.Async.Execute != null && de.Async.Schedule != null )
                        {
                            double scheduleTime = de.Async.Schedule.StartTime;
                            double executeTime = de.Async.Execute.Evt.StartTime;
                            double endExecuteTime = de.Async.Execute.Evt.EndTime;
                            if ( endExecuteTime >= scheduleTime )
                                graph.AddPoint( endExecuteTime, endExecuteTime - scheduleTime );
                        }
                    }
                }
            }
        }


        private void CreateAsyncAccumulatedWaitTimeGraph( Graph graph ) // Async accumulated wait time
        {
            double accumulatedTime = 0.0;
            foreach ( var e in allEvents )
            {
                if ( e.Value.Evt.Type == RawEventType.BlockStart )
                {
                    BlockType type = (BlockType)e.Value.Evt.Def.ID;
                    if ( type == BlockType.FileAsyncRead )
                    {
                        FileSystemEvent de = (FileSystemEvent)e.Value;
                        if ( de.Async.Execute != null && de.Async.Schedule != null )
                        {
                            double scheduleTime = de.Async.Schedule.StartTime;
                            double executeTime = de.Async.Execute.Evt.StartTime;
                            double endExecuteTime = de.Async.Execute.Evt.EndTime;
                            if ( endExecuteTime >= scheduleTime )
                            {
                                accumulatedTime += (endExecuteTime - scheduleTime);
                                graph.AddPoint( endExecuteTime, accumulatedTime );
                            }
                        }
                    }
                }
            }
        }

        private void CreateAsyncAbsoluteSpeedGraph( Graph graph ) // Async absolute speed
        {
            foreach ( var e in allEvents )
            {
                if ( e.Value.Evt.Type == RawEventType.BlockStart )
                {
                    BlockType type = (BlockType)e.Value.Evt.Def.ID;
                    if ( type == BlockType.FileAsyncRead )
                    {
                        FileSystemEvent de = (FileSystemEvent)e.Value;
                        if ( de.Async.Execute != null && de.Async.Execute.GetLength() > 0.0 )
                        {
                            double speed = de.Async.Size / de.Async.Execute.GetLength();
                            graph.AddPoint( de.Async.Execute.Evt.StartTime, speed );
                        }
                    }
                }
            }
        }

        public override string[] GetStatGraphsNames()
        {
            return new string[] {
                "Compressed data size",
                "Compressed data blocks",
                "Compression time",
                "Compression speed",
                "Compression ratio",
                "Read size histogram Lin1K",
                "Read size histogram Lin4K",
                "Read size histogram Lin16K",
                "Read time histogram Lin1K",
                "Read time histogram Lin4K",
                "Read time histogram Lin16K",
                "Read speed histogram Lin1K",
                "Read speed histogram Lin4K",
                "Read speed histogram Lin16K",
                "Read count histogram Lin1K",
                "Read count histogram Lin4K",
                "Read count histogram Lin16K",
                "Read avg. time histogram Lin1K",
                "Read avg. time histogram Lin4K",
                "Read avg. time histogram Lin16K",
                "File read blocks",
                "File read size",
                "File read time",
                "IO Tag blocks",
                "IO Tag size",
                "IO Tag time",
                "IO Tag speed",
            };
        }

        public static string[] GetCompressionNames()
        {
            return new string[]
            {
                "None", "zlib", "snappy", "doboz", "lz4", "lz4hc", "c-zlib",
            };
        }

        public static string[] GetIOTagNames()
        {
            return new string[]
            {
                "Generic", "BundlePreload",
                "ResourceLow", "ResourceNormal", "ResourceImmediate",
                "TexturesLow", "TexturesNormal", "TexturesImmediate",
                "MeshesLow", "MeshesNormal", "MeshesImmediate",
                "AnimationsLow", "AnimationsNormal", "AnimationsImmediate",
                "SoundNormal", "SoundImmediate", "CollisionNormal", "CollisionImmediate",
                "TerrainHeight", "TerrainControl", "TerrainColor",
                "UmbraBuffer", "StreamingData"
            };
        }

        public override string[] GetBlockHitDataNames()
        {
            return new string[] { "File read blocks" };
        }
    }
}
